Using jsPsych and cognition.run

Session 6 - JsPsych without cognition.run


Translations available

Disclaimer: may not be very accurate…


Worksheet overview

Aims

By the end of this worksheet you should be able to:

  • program your own experiments in jsPsych
  • host the experiment online using cognition.run
  • use the participant data for analysis
  • apply the basic skills you have learnt for your own purposes
  • learn some extra skills such as HTML, javascript, CSS and JSON

Pre-requisites

To complete the aims you will need to:

  • follow this worksheet
  • ask questions if you are not sure/be able to google
  • have a working computer and internet connection
  • be patient when things do not work

You do not need to:

  • have any programming knowledge
  • have high computer literacy
  • know anything about jsPsych, cognition.run, html, css or javascript
  • be a linguist

Structure

The worksheet will go through the following sections:

- working with randomisation
- working with data
- working with R to process your data

Recap

In the last session we should have:

  • Use audio and visual stimuli together
  • preload our stimuli
  • create a fully working experiment

JsPsych without cognition.run

We will now be moving away from cognition.run as a platform for hosting and running experiments. This is largely because it is simply not as useful for more complex, larger or secure data collection.

There are some differences between how your code will look in cognition.run and how it will look when you are writing a normal JsPsych experiment.

Here is how a really simple experiment looks in cognition.run:

// inititate jspsych
var jsPsych = initJsPsych();

// start timeline
timeline = [];

// define welcome message trial
var welcome = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: "Welcome to the experiment. Press any key to begin."
};

// push to timeline
timeline.push(welcome);

//run the experiment
jsPsych.run(timeline);

We do not need to load anything in to make this experiment work, this is because cognition.run does some of the important things in the background, meaning you do not have to write extra code.

However, in a normal JsPsych script things look a bit different.

experiment.html

Firstly, we do not use an interface on the internet to run the experiment. Instead, we need to create a directory on our computer that will have all the required files to make the experiment run.

The first file you will need is a .html, which will contain most of your code. These types of files are normally how webpages are written, for example see https://www.w3schools.com/html/tryit.asp?filename=tryhtml_editor

But for a JsPsych experiment, we need to write the html file in a specific way, so that we can load in JsPsych, plugins, and other important aspects so that our code will work.

First, we need to create a .html file. There are lots of ways to do this, you can use a text editor like atom https://atom-editor.cc/, or if you want to keep things simple, we can use R.

Open RStudio and create a new text file:

Now create a folder somewhere on your computer and call it jspsych_experiment_demo, then save your text file in R as experiment.html in the folder. Now we can start writing our script.

A .html file needs the following basic structure:

<!DOCTYPE html>
<html>
<head>
  <title>Page Title</title>
</head>
<body>
</body>

</html>
  • <!DOCTYPE html>: This is the document type declaration, which informs the web browser that the document is an HTML5 document. It is not an HTML tag; it’s an instruction to the browser.

  • <html>: This is the opening tag of the HTML document. It indicates the start of the HTML document and encapsulates all the HTML content.

  • <head>: This is the opening tag of the head section of the HTML document. The head section contains meta-information about the document, such as its title, links to stylesheets, and metadata.

  • <title>Page Title</title>: This is a title element within the head section. It sets the title of the HTML document, which appears in the browser’s title bar or tab.

  • </head>: This is the closing tag of the head section. It indicates the end of the head section and the beginning of the body section.

  • <body>: This is the opening tag of the body section of the HTML document. The body section contains the main content of the document, such as text, images, and other media.

  • </body>: This is the closing tag of the body section. It indicates the end of the body section.

  • </html>: This is the closing tag of the HTML document. It indicates the end of the HTML document.

JsPsych scripts

In order for our experiment to work with JsPsych, we need to add some code so that the file can read in scripts. JsPsych has a specific set of files that are required to run an experiment, but first we need to download them.

You can download the folder containing these files JsPsych 7.3.4 at this link https://github.com/jspsych/jsPsych/releases/download/jspsych%407.3.4/jspsych.zip

They are also in the OneDrive folder jspsych.

Make sure you put this folder in the jspsych_experiment_demo folder on your computer.

Note, JsPsych is constantly being developed, so this version of the files may get updated over time. You can check on updates at the JsPsysch GitHub https://github.com/jspsych/jsPsych/releases. If you download these files and store them on your computer, they can be shared with your scripts, but make sure to write somewhere which version of JsPsych you are using.

Within the folder you will see a subfolder called dist, this has all the required files for a basic experiment to run.

  • jspsych.js is the most important file, it allows all the basic functions of JsPsych to be used

  • jspsych.css is the css styling file, it makes everything look a bit nicer than the default

  • plugin-___.js are the plugin files, each plugin will have an individual file that needs to be loaded if being used in the experiment

To load these files we will add some code to our experiment.html file within the <head> tag. It will follow this format:

<script src="filepath/filename.js"></script>
  • filepath is the filepath for where the file we want to load is located. Our experiment.html file uses the folder it is located in as the root directory, i.e. you do not need to specify desktop/my_files/jspsych/jspsych_experiment_demo/ you can just write jspsych/dist/

  • filename.js is the name of the file you want to load, e.g. jspsych.js

So if we want to load in jspsych.js then we would use:

<script src="jspsych/dist/jspsych.js"></script>

If we want to load in another file, we have to write the same code, but changing the filename. E.g. to load in the files jspsych.js and plugin-html-keyboard-response.js we would need to have:

<script src="jspsych/dist/jspsych.js"></script>
<script src="jspsych/dist/plugin-html-keyboard-response.js"></script>

This is an important difference between cognition.run, as you need to know what plugins you are using and add the code that loads in the plugin from the individual file.

If we want to load in the css file jspsych.css then the code will look a little bit different, as it is not a .js file:

<script src="jspsych/dist/jspsych.js"></script>
<script src="jspsych/dist/plugin-html-keyboard-response.js"></script>
<link href="jspsych/dist/jspsych.css" rel="stylesheet" type="text/css" />

Our experiment.html file should now look like this:

<!DOCTYPE html>
<html>
<head>
  <title>Page Title</title>
  <script src="jspsych/dist/jspsych.js"></script>
  <script src="jspsych/dist/plugin-html-keyboard-response.js"></script>
  <link href="jspsych/dist/jspsych.css" rel="stylesheet" type="text/css" />
</head>
<body>
</body>

</html>

JsPsych code

Now we can start writing in our JsPsych code.

Underneath the closing body tag </body> we need to write our own script, so we will add a script tag and then put some JsPsych code within the tag. Here we can just run a really simple experiment with the jsPsychHtmlKeyboardResponse plugin:

<script>
  
// inititate jspsych
var jsPsych = initJsPsych();

// start timeline
timeline = [];

// define welcome message trial
var welcome = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: "Welcome to the experiment. Press any key to begin."
};

// push to timeline
timeline.push(welcome);

//run the experiment
jsPsych.run(timeline);

</script>

If you save the experiment.html file, and then open it in a web browser you should see that the experiment works.

Here is how your experiment.html file should look:

<!DOCTYPE html>
<html>
<head>
  <title>Page Title</title>
  <script src="jspsych/dist/jspsych.js"></script>
  <script src="jspsych/dist/plugin-html-keyboard-response.js"></script>
  <link href="jspsych/dist/jspsych.css" rel="stylesheet" type="text/css" />
</head>
<body>
</body>

<script>

// inititate jspsych
var jsPsych = initJsPsych();

// start timeline
timeline = [];

// define welcome message trial
var welcome = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: "Welcome to the experiment. Press any key to begin."
};

// push to timeline
timeline.push(welcome);

//run the experiment
jsPsych.run(timeline);
  
</script>

</html>
LS0tCnRpdGxlOiAiVXNpbmcganNQc3ljaCBhbmQgY29nbml0aW9uLnJ1biIKc3VidGl0bGU6ICJTZXNzaW9uIDYgLSBKc1BzeWNoIHdpdGhvdXQgY29nbml0aW9uLnJ1biIKYXV0aG9yOiAiSmFtZXMgQnJhbmQiCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCLCAlWScpYCIKb3V0cHV0OgogIHJtZGZvcm1hdHM6OnJlYWR0aGVkb3duOgogICAgcGFuZG9jX2FyZ3M6ICItLWhpZ2hsaWdodC1zdHlsZT1teS50aGVtZSIKICAgIGhpZ2hsaWdodDogcHlnbWVudHMKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICBjb2xsYXBzZWQ6IGZhbHNlCiAgICBkZl9wcmludDogcGFnZWQKICAgIGxpZ2h0Ym94OiBUUlVFCiAgICBnYWxsZXJ5OiBUUlVFCiAgICBjc3M6ICJjc3Mvc3R5bGUuY3NzIgogICAgCi0tLQoKYGBge3IgZWNobz1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShzbGlja1IpCmxpYnJhcnkoaHRtbHRvb2xzKQpsaWJyYXJ5KHhhcmluZ2FuRXh0cmEpCmxpYnJhcnkocm1hcmtkb3duKQpsaWJyYXJ5KGZvbnRhd2Vzb21lKQpsaWJyYXJ5KGJzcGx1cykKbGlicmFyeShEVCkKCmBgYAoKYGBge3Igc2V0dXAsIHdhcm5pbmc9RkFMU0UsIGVjaG89RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgIGV2YWwgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIGNvbW1lbnQgPSBOQSwKICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIHdhcm5pbmcgPSBGQUxTRSkKCmtuaXRyOjprbml0X2hvb2tzJHNldCgKICBtZXNzYWdlID0gZnVuY3Rpb24oeCwgb3B0aW9ucykgewogICAgIHBhc3RlKCc8YnV0dG9uIHR5cGU9ImJ1dHRvbiIgY2xhc3M9ImNvbGxhcHNpYmxlMSI+PHN0cm9uZz4nLAogICAgIGZhKG5hbWUgPSAiY2lyY2xlLWluZm8iKSwKICAgICAnIG1vcmUgaW5mbzwvc3Ryb25nPjwvYnV0dG9uPicsICc8ZGl2IGNsYXNzPSJjb250ZW50MSI+PHA+JywKICAgICBnc3ViKCcjIycsICdcbicsIHgpLAogICAgICc8L3A+PC9kaXY+JywKICAgICBzZXAgPSAnXG4nKQogICB9KQoKY29kZWJsb2NrID0gZnVuY3Rpb24oeCwgb3B0aW9ucykgewogICAgIGNhdChwYXN0ZSgnPGRpdiBjbGFzcz0iY29kZWJsb2NrIj4nLAogICAgIHBhc3RlMCh4KSwKICAgICAnPC9kaXY+JywKICAgICBzZXAgPSAnXG4nKSkKICAgfQoKYGBgCgotLS0KCiMjIGByIGZhKCJsYW5ndWFnZSIpYCBUcmFuc2xhdGlvbnMgYXZhaWxhYmxlCgpEaXNjbGFpbWVyOiBtYXkgbm90IGJlIHZlcnkgYWNjdXJhdGUuLi4KCjxkaXYgaWQ9Imdvb2dsZV90cmFuc2xhdGVfZWxlbWVudCI+PC9kaXY+CgotLS0KCiMgV29ya3NoZWV0IG92ZXJ2aWV3CgojIyBgciBmYSgiY3Jvc3NoYWlycyIpYCBBaW1zCgpCeSB0aGUgZW5kIG9mIHRoaXMgd29ya3NoZWV0IHlvdSBzaG91bGQgYmUgYWJsZSB0bzoKCi0gKipwcm9ncmFtKiogeW91ciBvd24gZXhwZXJpbWVudHMgaW4ganNQc3ljaAotICoqaG9zdCoqIHRoZSBleHBlcmltZW50IG9ubGluZSB1c2luZyBjb2duaXRpb24ucnVuCi0gKip1c2UqKiB0aGUgcGFydGljaXBhbnQgZGF0YSBmb3IgYW5hbHlzaXMKLSAqKmFwcGx5KiogdGhlIGJhc2ljIHNraWxscyB5b3UgaGF2ZSBsZWFybnQgZm9yIHlvdXIgb3duIHB1cnBvc2VzCi0gKipsZWFybioqIHNvbWUgZXh0cmEgc2tpbGxzIHN1Y2ggYXMgSFRNTCwgamF2YXNjcmlwdCwgQ1NTIGFuZCBKU09OCgojIyBgciBmYSgidXNlci1ncmFkdWF0ZSIpYCBQcmUtcmVxdWlzaXRlcwoKVG8gY29tcGxldGUgdGhlIGFpbXMgeW91IHdpbGwgbmVlZCB0bzoKCi0gKipmb2xsb3cqKiB0aGlzIHdvcmtzaGVldAotICoqYXNrKiogcXVlc3Rpb25zIGlmIHlvdSBhcmUgbm90IHN1cmUvYmUgYWJsZSB0byBnb29nbGUKLSAqKmhhdmUqKiBhIHdvcmtpbmcgY29tcHV0ZXIgYW5kIGludGVybmV0IGNvbm5lY3Rpb24KLSAqKmJlIHBhdGllbnQqKiB3aGVuIHRoaW5ncyBkbyBub3Qgd29yawoKWW91IGRvIG5vdCBuZWVkIHRvOgoKLSBoYXZlIGFueSAqKnByb2dyYW1taW5nIGtub3dsZWRnZSoqCi0gaGF2ZSBoaWdoICoqY29tcHV0ZXIgbGl0ZXJhY3kqKgotIGtub3cgYW55dGhpbmcgYWJvdXQgKipqc1BzeWNoLCBjb2duaXRpb24ucnVuLCBodG1sLCBjc3Mgb3IgamF2YXNjcmlwdCoqCi0gYmUgYSAqKmxpbmd1aXN0KioKCiMjIGByIGZhKCJmb2xkZXItdHJlZSIpYCBTdHJ1Y3R1cmUKClRoZSB3b3Jrc2hlZXQgd2lsbCBnbyB0aHJvdWdoIHRoZSBmb2xsb3dpbmcgc2VjdGlvbnM6CgogICAgLSB3b3JraW5nIHdpdGggcmFuZG9taXNhdGlvbgogICAgLSB3b3JraW5nIHdpdGggZGF0YQogICAgLSB3b3JraW5nIHdpdGggUiB0byBwcm9jZXNzIHlvdXIgZGF0YQoKIyMgYHIgZmEoImxpZ2h0YnVsYiIpYCBSZWNhcAoKSW4gdGhlIGxhc3Qgc2Vzc2lvbiB3ZSBzaG91bGQgaGF2ZToKCi0gVXNlIGF1ZGlvIGFuZCB2aXN1YWwgc3RpbXVsaSB0b2dldGhlcgotIHByZWxvYWQgb3VyIHN0aW11bGkKLSBjcmVhdGUgYSBmdWxseSB3b3JraW5nIGV4cGVyaW1lbnQKCi0tLQoKCiMgSnNQc3ljaCB3aXRob3V0IGNvZ25pdGlvbi5ydW4KCldlIHdpbGwgbm93IGJlIG1vdmluZyBhd2F5IGZyb20gY29nbml0aW9uLnJ1biBhcyBhIHBsYXRmb3JtIGZvciBob3N0aW5nIGFuZCBydW5uaW5nIGV4cGVyaW1lbnRzLiBUaGlzIGlzIGxhcmdlbHkgYmVjYXVzZSBpdCBpcyBzaW1wbHkgbm90IGFzIHVzZWZ1bCBmb3IgbW9yZSBjb21wbGV4LCBsYXJnZXIgb3Igc2VjdXJlIGRhdGEgY29sbGVjdGlvbi4KClRoZXJlIGFyZSBzb21lIGRpZmZlcmVuY2VzIGJldHdlZW4gaG93IHlvdXIgY29kZSB3aWxsIGxvb2sgaW4gY29nbml0aW9uLnJ1biBhbmQgaG93IGl0IHdpbGwgbG9vayB3aGVuIHlvdSBhcmUgd3JpdGluZyBhIG5vcm1hbCBKc1BzeWNoIGV4cGVyaW1lbnQuCgpIZXJlIGlzIGhvdyBhIHJlYWxseSBzaW1wbGUgZXhwZXJpbWVudCBsb29rcyBpbiBjb2duaXRpb24ucnVuOgoKYGBge2pzfQovLyBpbml0aXRhdGUganNwc3ljaAp2YXIganNQc3ljaCA9IGluaXRKc1BzeWNoKCk7CgovLyBzdGFydCB0aW1lbGluZQp0aW1lbGluZSA9IFtdOwoKLy8gZGVmaW5lIHdlbGNvbWUgbWVzc2FnZSB0cmlhbAp2YXIgd2VsY29tZSA9IHsKICB0eXBlOiBqc1BzeWNoSHRtbEtleWJvYXJkUmVzcG9uc2UsCiAgc3RpbXVsdXM6ICJXZWxjb21lIHRvIHRoZSBleHBlcmltZW50LiBQcmVzcyBhbnkga2V5IHRvIGJlZ2luLiIKfTsKCi8vIHB1c2ggdG8gdGltZWxpbmUKdGltZWxpbmUucHVzaCh3ZWxjb21lKTsKCi8vcnVuIHRoZSBleHBlcmltZW50CmpzUHN5Y2gucnVuKHRpbWVsaW5lKTsKCmBgYAoKV2UgZG8gbm90IG5lZWQgdG8gbG9hZCBhbnl0aGluZyBpbiB0byBtYWtlIHRoaXMgZXhwZXJpbWVudCB3b3JrLCB0aGlzIGlzIGJlY2F1c2UgY29nbml0aW9uLnJ1biBkb2VzIHNvbWUgb2YgdGhlIGltcG9ydGFudCB0aGluZ3MgaW4gdGhlIGJhY2tncm91bmQsIG1lYW5pbmcgeW91IGRvIG5vdCBoYXZlIHRvIHdyaXRlIGV4dHJhIGNvZGUuCgpIb3dldmVyLCBpbiBhIG5vcm1hbCBKc1BzeWNoIHNjcmlwdCB0aGluZ3MgbG9vayBhIGJpdCBkaWZmZXJlbnQuCgojIGV4cGVyaW1lbnQuaHRtbAoKRmlyc3RseSwgd2UgZG8gbm90IHVzZSBhbiBpbnRlcmZhY2Ugb24gdGhlIGludGVybmV0IHRvIHJ1biB0aGUgZXhwZXJpbWVudC4gSW5zdGVhZCwgd2UgbmVlZCB0byBjcmVhdGUgYSBkaXJlY3Rvcnkgb24gb3VyIGNvbXB1dGVyIHRoYXQgd2lsbCBoYXZlIGFsbCB0aGUgcmVxdWlyZWQgZmlsZXMgdG8gbWFrZSB0aGUgZXhwZXJpbWVudCBydW4uCgpUaGUgZmlyc3QgZmlsZSB5b3Ugd2lsbCBuZWVkIGlzIGEgYC5odG1sYCwgd2hpY2ggd2lsbCBjb250YWluIG1vc3Qgb2YgeW91ciBjb2RlLiBUaGVzZSB0eXBlcyBvZiBmaWxlcyBhcmUgbm9ybWFsbHkgaG93IHdlYnBhZ2VzIGFyZSB3cml0dGVuLCBmb3IgZXhhbXBsZSBzZWUgaHR0cHM6Ly93d3cudzNzY2hvb2xzLmNvbS9odG1sL3RyeWl0LmFzcD9maWxlbmFtZT10cnlodG1sX2VkaXRvcgoKQnV0IGZvciBhIEpzUHN5Y2ggZXhwZXJpbWVudCwgd2UgbmVlZCB0byB3cml0ZSB0aGUgaHRtbCBmaWxlIGluIGEgc3BlY2lmaWMgd2F5LCBzbyB0aGF0IHdlIGNhbiBsb2FkIGluIEpzUHN5Y2gsIHBsdWdpbnMsIGFuZCBvdGhlciBpbXBvcnRhbnQgYXNwZWN0cyBzbyB0aGF0IG91ciBjb2RlIHdpbGwgd29yay4KCkZpcnN0LCB3ZSBuZWVkIHRvIGNyZWF0ZSBhIGAuaHRtbGAgZmlsZS4gVGhlcmUgYXJlIGxvdHMgb2Ygd2F5cyB0byBkbyB0aGlzLCB5b3UgY2FuIHVzZSBhIHRleHQgZWRpdG9yIGxpa2UgYGF0b21gIGh0dHBzOi8vYXRvbS1lZGl0b3IuY2MvLCBvciBpZiB5b3Ugd2FudCB0byBrZWVwIHRoaW5ncyBzaW1wbGUsIHdlIGNhbiB1c2UgUi4KCk9wZW4gUlN0dWRpbyBhbmQgY3JlYXRlIGEgbmV3IGB0ZXh0IGZpbGVgOgoKIVtdKGltYWdlcy90ZXh0X2ZpbGVfci5wbmcpCgpOb3cgY3JlYXRlIGEgZm9sZGVyIHNvbWV3aGVyZSBvbiB5b3VyIGNvbXB1dGVyIGFuZCBjYWxsIGl0IGBqc3BzeWNoX2V4cGVyaW1lbnRfZGVtb2AsIHRoZW4gc2F2ZSB5b3VyIHRleHQgZmlsZSBpbiBSIGFzIGBleHBlcmltZW50Lmh0bWxgIGluIHRoZSBmb2xkZXIuIE5vdyB3ZSBjYW4gc3RhcnQgd3JpdGluZyBvdXIgc2NyaXB0LgoKQSBgLmh0bWxgIGZpbGUgbmVlZHMgdGhlIGZvbGxvd2luZyBiYXNpYyBzdHJ1Y3R1cmU6CgpgYGB7aHRtbH0KPCFET0NUWVBFIGh0bWw+CjxodG1sPgo8aGVhZD4KICA8dGl0bGU+UGFnZSBUaXRsZTwvdGl0bGU+CjwvaGVhZD4KPGJvZHk+CjwvYm9keT4KCjwvaHRtbD4KCmBgYAoKLSBgPCFET0NUWVBFIGh0bWw+YDogVGhpcyBpcyB0aGUgZG9jdW1lbnQgdHlwZSBkZWNsYXJhdGlvbiwgd2hpY2ggaW5mb3JtcyB0aGUgd2ViIGJyb3dzZXIgdGhhdCB0aGUgZG9jdW1lbnQgaXMgYW4gSFRNTDUgZG9jdW1lbnQuIEl0IGlzIG5vdCBhbiBIVE1MIHRhZzsgaXQncyBhbiBpbnN0cnVjdGlvbiB0byB0aGUgYnJvd3Nlci4KCi0gYDxodG1sPmA6IFRoaXMgaXMgdGhlIG9wZW5pbmcgdGFnIG9mIHRoZSBIVE1MIGRvY3VtZW50LiBJdCBpbmRpY2F0ZXMgdGhlIHN0YXJ0IG9mIHRoZSBIVE1MIGRvY3VtZW50IGFuZCBlbmNhcHN1bGF0ZXMgYWxsIHRoZSBIVE1MIGNvbnRlbnQuCgotIGA8aGVhZD5gOiBUaGlzIGlzIHRoZSBvcGVuaW5nIHRhZyBvZiB0aGUgaGVhZCBzZWN0aW9uIG9mIHRoZSBIVE1MIGRvY3VtZW50LiBUaGUgaGVhZCBzZWN0aW9uIGNvbnRhaW5zIG1ldGEtaW5mb3JtYXRpb24gYWJvdXQgdGhlIGRvY3VtZW50LCBzdWNoIGFzIGl0cyB0aXRsZSwgbGlua3MgdG8gc3R5bGVzaGVldHMsIGFuZCBtZXRhZGF0YS4KCi0gYDx0aXRsZT5QYWdlIFRpdGxlPC90aXRsZT5gOiBUaGlzIGlzIGEgdGl0bGUgZWxlbWVudCB3aXRoaW4gdGhlIGhlYWQgc2VjdGlvbi4gSXQgc2V0cyB0aGUgdGl0bGUgb2YgdGhlIEhUTUwgZG9jdW1lbnQsIHdoaWNoIGFwcGVhcnMgaW4gdGhlIGJyb3dzZXIncyB0aXRsZSBiYXIgb3IgdGFiLgoKLSBgPC9oZWFkPmA6IFRoaXMgaXMgdGhlIGNsb3NpbmcgdGFnIG9mIHRoZSBoZWFkIHNlY3Rpb24uIEl0IGluZGljYXRlcyB0aGUgZW5kIG9mIHRoZSBoZWFkIHNlY3Rpb24gYW5kIHRoZSBiZWdpbm5pbmcgb2YgdGhlIGJvZHkgc2VjdGlvbi4KCi0gYDxib2R5PmA6IFRoaXMgaXMgdGhlIG9wZW5pbmcgdGFnIG9mIHRoZSBib2R5IHNlY3Rpb24gb2YgdGhlIEhUTUwgZG9jdW1lbnQuIFRoZSBib2R5IHNlY3Rpb24gY29udGFpbnMgdGhlIG1haW4gY29udGVudCBvZiB0aGUgZG9jdW1lbnQsIHN1Y2ggYXMgdGV4dCwgaW1hZ2VzLCBhbmQgb3RoZXIgbWVkaWEuCgotIGA8L2JvZHk+YDogVGhpcyBpcyB0aGUgY2xvc2luZyB0YWcgb2YgdGhlIGJvZHkgc2VjdGlvbi4gSXQgaW5kaWNhdGVzIHRoZSBlbmQgb2YgdGhlIGJvZHkgc2VjdGlvbi4KCi0gYDwvaHRtbD5gOiBUaGlzIGlzIHRoZSBjbG9zaW5nIHRhZyBvZiB0aGUgSFRNTCBkb2N1bWVudC4gSXQgaW5kaWNhdGVzIHRoZSBlbmQgb2YgdGhlIEhUTUwgZG9jdW1lbnQuCgojIEpzUHN5Y2ggc2NyaXB0cwoKSW4gb3JkZXIgZm9yIG91ciBleHBlcmltZW50IHRvIHdvcmsgd2l0aCBKc1BzeWNoLCB3ZSBuZWVkIHRvIGFkZCBzb21lIGNvZGUgc28gdGhhdCB0aGUgZmlsZSBjYW4gcmVhZCBpbiBgc2NyaXB0c2AuIEpzUHN5Y2ggaGFzIGEgc3BlY2lmaWMgc2V0IG9mIGZpbGVzIHRoYXQgYXJlIHJlcXVpcmVkIHRvIHJ1biBhbiBleHBlcmltZW50LCBidXQgZmlyc3Qgd2UgbmVlZCB0byBkb3dubG9hZCB0aGVtLgoKWW91IGNhbiBkb3dubG9hZCB0aGUgZm9sZGVyIGNvbnRhaW5pbmcgdGhlc2UgZmlsZXMgSnNQc3ljaCA3LjMuNCBhdCB0aGlzIGxpbmsgaHR0cHM6Ly9naXRodWIuY29tL2pzcHN5Y2gvanNQc3ljaC9yZWxlYXNlcy9kb3dubG9hZC9qc3BzeWNoJTQwNy4zLjQvanNwc3ljaC56aXAKClRoZXkgYXJlIGFsc28gaW4gdGhlIE9uZURyaXZlIGZvbGRlciBganNwc3ljaGAuCgpNYWtlIHN1cmUgeW91IHB1dCB0aGlzIGZvbGRlciBpbiB0aGUgYGpzcHN5Y2hfZXhwZXJpbWVudF9kZW1vYCBmb2xkZXIgb24geW91ciBjb21wdXRlci4KCk5vdGUsIEpzUHN5Y2ggaXMgY29uc3RhbnRseSBiZWluZyBkZXZlbG9wZWQsIHNvIHRoaXMgdmVyc2lvbiBvZiB0aGUgZmlsZXMgbWF5IGdldCB1cGRhdGVkIG92ZXIgdGltZS4gWW91IGNhbiBjaGVjayBvbiB1cGRhdGVzIGF0IHRoZSBKc1BzeXNjaCBHaXRIdWIgaHR0cHM6Ly9naXRodWIuY29tL2pzcHN5Y2gvanNQc3ljaC9yZWxlYXNlcy4gSWYgeW91IGRvd25sb2FkIHRoZXNlIGZpbGVzIGFuZCBzdG9yZSB0aGVtIG9uIHlvdXIgY29tcHV0ZXIsIHRoZXkgY2FuIGJlIHNoYXJlZCB3aXRoIHlvdXIgc2NyaXB0cywgYnV0IG1ha2Ugc3VyZSB0byB3cml0ZSBzb21ld2hlcmUgd2hpY2ggdmVyc2lvbiBvZiBKc1BzeWNoIHlvdSBhcmUgdXNpbmcuCgpXaXRoaW4gdGhlIGZvbGRlciB5b3Ugd2lsbCBzZWUgYSBzdWJmb2xkZXIgY2FsbGVkIGBkaXN0YCwgdGhpcyBoYXMgYWxsIHRoZSByZXF1aXJlZCBmaWxlcyBmb3IgYSBiYXNpYyBleHBlcmltZW50IHRvIHJ1bi4KCiFbXShpbWFnZXMvanNwc3ljaF9kaXN0LnBuZykKCi0gYGpzcHN5Y2guanNgIGlzIHRoZSBtb3N0IGltcG9ydGFudCBmaWxlLCBpdCBhbGxvd3MgYWxsIHRoZSBiYXNpYyBmdW5jdGlvbnMgb2YgSnNQc3ljaCB0byBiZSB1c2VkCgotIGBqc3BzeWNoLmNzc2AgaXMgdGhlIGNzcyBzdHlsaW5nIGZpbGUsIGl0IG1ha2VzIGV2ZXJ5dGhpbmcgbG9vayBhIGJpdCBuaWNlciB0aGFuIHRoZSBkZWZhdWx0CgotIGBwbHVnaW4tX19fLmpzYCBhcmUgdGhlIHBsdWdpbiBmaWxlcywgZWFjaCBwbHVnaW4gd2lsbCBoYXZlIGFuIGluZGl2aWR1YWwgZmlsZSB0aGF0IG5lZWRzIHRvIGJlIGxvYWRlZCBpZiBiZWluZyB1c2VkIGluIHRoZSBleHBlcmltZW50CgpUbyBsb2FkIHRoZXNlIGZpbGVzIHdlIHdpbGwgYWRkIHNvbWUgY29kZSB0byBvdXIgYGV4cGVyaW1lbnQuaHRtbGAgZmlsZSB3aXRoaW4gdGhlIGA8aGVhZD5gIHRhZy4gSXQgd2lsbCBmb2xsb3cgdGhpcyBmb3JtYXQ6CgpgYGB7aHRtbH0KPHNjcmlwdCBzcmM9ImZpbGVwYXRoL2ZpbGVuYW1lLmpzIj48L3NjcmlwdD4KCmBgYAoKLSBgZmlsZXBhdGhgIGlzIHRoZSBmaWxlcGF0aCBmb3Igd2hlcmUgdGhlIGZpbGUgd2Ugd2FudCB0byBsb2FkIGlzIGxvY2F0ZWQuIE91ciBgZXhwZXJpbWVudC5odG1sYCBmaWxlIHVzZXMgdGhlIGZvbGRlciBpdCBpcyBsb2NhdGVkIGluIGFzIHRoZSByb290IGRpcmVjdG9yeSwgaS5lLiB5b3UgZG8gbm90IG5lZWQgdG8gc3BlY2lmeSBgZGVza3RvcC9teV9maWxlcy9qc3BzeWNoL2pzcHN5Y2hfZXhwZXJpbWVudF9kZW1vL2AgeW91IGNhbiBqdXN0IHdyaXRlIGBqc3BzeWNoL2Rpc3QvYAoKLSBgZmlsZW5hbWUuanNgIGlzIHRoZSBuYW1lIG9mIHRoZSBmaWxlIHlvdSB3YW50IHRvIGxvYWQsIGUuZy4gYGpzcHN5Y2guanNgCgpTbyBpZiB3ZSB3YW50IHRvIGxvYWQgaW4gYGpzcHN5Y2guanNgIHRoZW4gd2Ugd291bGQgdXNlOgoKYGBge2h0bWx9CjxzY3JpcHQgc3JjPSJqc3BzeWNoL2Rpc3QvanNwc3ljaC5qcyI+PC9zY3JpcHQ+CgpgYGAKCklmIHdlIHdhbnQgdG8gbG9hZCBpbiBhbm90aGVyIGZpbGUsIHdlIGhhdmUgdG8gd3JpdGUgdGhlIHNhbWUgY29kZSwgYnV0IGNoYW5naW5nIHRoZSBmaWxlbmFtZS4gRS5nLiB0byBsb2FkIGluIHRoZSBmaWxlcyBganNwc3ljaC5qc2AgYW5kIGBwbHVnaW4taHRtbC1rZXlib2FyZC1yZXNwb25zZS5qc2Agd2Ugd291bGQgbmVlZCB0byBoYXZlOgoKYGBge2h0bWx9CjxzY3JpcHQgc3JjPSJqc3BzeWNoL2Rpc3QvanNwc3ljaC5qcyI+PC9zY3JpcHQ+CjxzY3JpcHQgc3JjPSJqc3BzeWNoL2Rpc3QvcGx1Z2luLWh0bWwta2V5Ym9hcmQtcmVzcG9uc2UuanMiPjwvc2NyaXB0PgoKYGBgCgpUaGlzIGlzIGFuIGltcG9ydGFudCBkaWZmZXJlbmNlIGJldHdlZW4gY29nbml0aW9uLnJ1biwgYXMgeW91IG5lZWQgdG8ga25vdyB3aGF0IHBsdWdpbnMgeW91IGFyZSB1c2luZyBhbmQgYWRkIHRoZSBjb2RlIHRoYXQgbG9hZHMgaW4gdGhlIHBsdWdpbiBmcm9tIHRoZSBpbmRpdmlkdWFsIGZpbGUuCgpJZiB3ZSB3YW50IHRvIGxvYWQgaW4gdGhlIGNzcyBmaWxlIGBqc3BzeWNoLmNzc2AgdGhlbiB0aGUgY29kZSB3aWxsIGxvb2sgYSBsaXR0bGUgYml0IGRpZmZlcmVudCwgYXMgaXQgaXMgbm90IGEgYC5qc2AgZmlsZToKCmBgYHtodG1sfQo8c2NyaXB0IHNyYz0ianNwc3ljaC9kaXN0L2pzcHN5Y2guanMiPjwvc2NyaXB0Pgo8c2NyaXB0IHNyYz0ianNwc3ljaC9kaXN0L3BsdWdpbi1odG1sLWtleWJvYXJkLXJlc3BvbnNlLmpzIj48L3NjcmlwdD4KPGxpbmsgaHJlZj0ianNwc3ljaC9kaXN0L2pzcHN5Y2guY3NzIiByZWw9InN0eWxlc2hlZXQiIHR5cGU9InRleHQvY3NzIiAvPgoKYGBgCgpPdXIgYGV4cGVyaW1lbnQuaHRtbGAgZmlsZSBzaG91bGQgbm93IGxvb2sgbGlrZSB0aGlzOgoKYGBge2h0bWx9CjwhRE9DVFlQRSBodG1sPgo8aHRtbD4KPGhlYWQ+CiAgPHRpdGxlPlBhZ2UgVGl0bGU8L3RpdGxlPgogIDxzY3JpcHQgc3JjPSJqc3BzeWNoL2Rpc3QvanNwc3ljaC5qcyI+PC9zY3JpcHQ+CiAgPHNjcmlwdCBzcmM9ImpzcHN5Y2gvZGlzdC9wbHVnaW4taHRtbC1rZXlib2FyZC1yZXNwb25zZS5qcyI+PC9zY3JpcHQ+CiAgPGxpbmsgaHJlZj0ianNwc3ljaC9kaXN0L2pzcHN5Y2guY3NzIiByZWw9InN0eWxlc2hlZXQiIHR5cGU9InRleHQvY3NzIiAvPgo8L2hlYWQ+Cjxib2R5Pgo8L2JvZHk+Cgo8L2h0bWw+CgpgYGAKCiMgSnNQc3ljaCBjb2RlCgpOb3cgd2UgY2FuIHN0YXJ0IHdyaXRpbmcgaW4gb3VyIEpzUHN5Y2ggY29kZS4KClVuZGVybmVhdGggdGhlIGNsb3NpbmcgYm9keSB0YWcgYDwvYm9keT5gIHdlIG5lZWQgdG8gd3JpdGUgb3VyIG93biBgc2NyaXB0YCwgc28gd2Ugd2lsbCBhZGQgYSBzY3JpcHQgdGFnIGFuZCB0aGVuIHB1dCBzb21lIEpzUHN5Y2ggY29kZSB3aXRoaW4gdGhlIHRhZy4gSGVyZSB3ZSBjYW4ganVzdCBydW4gYSByZWFsbHkgc2ltcGxlIGV4cGVyaW1lbnQgd2l0aCB0aGUgYGpzUHN5Y2hIdG1sS2V5Ym9hcmRSZXNwb25zZWAgcGx1Z2luOgoKYGBge2h0bWx9CjxzY3JpcHQ+CiAgCi8vIGluaXRpdGF0ZSBqc3BzeWNoCnZhciBqc1BzeWNoID0gaW5pdEpzUHN5Y2goKTsKCi8vIHN0YXJ0IHRpbWVsaW5lCnRpbWVsaW5lID0gW107CgovLyBkZWZpbmUgd2VsY29tZSBtZXNzYWdlIHRyaWFsCnZhciB3ZWxjb21lID0gewogIHR5cGU6IGpzUHN5Y2hIdG1sS2V5Ym9hcmRSZXNwb25zZSwKICBzdGltdWx1czogIldlbGNvbWUgdG8gdGhlIGV4cGVyaW1lbnQuIFByZXNzIGFueSBrZXkgdG8gYmVnaW4uIgp9OwoKLy8gcHVzaCB0byB0aW1lbGluZQp0aW1lbGluZS5wdXNoKHdlbGNvbWUpOwoKLy9ydW4gdGhlIGV4cGVyaW1lbnQKanNQc3ljaC5ydW4odGltZWxpbmUpOwoKPC9zY3JpcHQ+CgpgYGAKCklmIHlvdSBzYXZlIHRoZSBgZXhwZXJpbWVudC5odG1sYCBmaWxlLCBhbmQgdGhlbiBvcGVuIGl0IGluIGEgd2ViIGJyb3dzZXIgeW91IHNob3VsZCBzZWUgdGhhdCB0aGUgZXhwZXJpbWVudCB3b3Jrcy4KCiFbXShpbWFnZXMvZXhwZXJpbWVudF9icm93c2VyLnBuZykKCkhlcmUgaXMgaG93IHlvdXIgYGV4cGVyaW1lbnQuaHRtbGAgZmlsZSBzaG91bGQgbG9vazoKCmBgYHtodG1sfQo8IURPQ1RZUEUgaHRtbD4KPGh0bWw+CjxoZWFkPgogIDx0aXRsZT5QYWdlIFRpdGxlPC90aXRsZT4KICA8c2NyaXB0IHNyYz0ianNwc3ljaC9kaXN0L2pzcHN5Y2guanMiPjwvc2NyaXB0PgogIDxzY3JpcHQgc3JjPSJqc3BzeWNoL2Rpc3QvcGx1Z2luLWh0bWwta2V5Ym9hcmQtcmVzcG9uc2UuanMiPjwvc2NyaXB0PgogIDxsaW5rIGhyZWY9ImpzcHN5Y2gvZGlzdC9qc3BzeWNoLmNzcyIgcmVsPSJzdHlsZXNoZWV0IiB0eXBlPSJ0ZXh0L2NzcyIgLz4KPC9oZWFkPgo8Ym9keT4KPC9ib2R5PgoKPHNjcmlwdD4KCi8vIGluaXRpdGF0ZSBqc3BzeWNoCnZhciBqc1BzeWNoID0gaW5pdEpzUHN5Y2goKTsKCi8vIHN0YXJ0IHRpbWVsaW5lCnRpbWVsaW5lID0gW107CgovLyBkZWZpbmUgd2VsY29tZSBtZXNzYWdlIHRyaWFsCnZhciB3ZWxjb21lID0gewogIHR5cGU6IGpzUHN5Y2hIdG1sS2V5Ym9hcmRSZXNwb25zZSwKICBzdGltdWx1czogIldlbGNvbWUgdG8gdGhlIGV4cGVyaW1lbnQuIFByZXNzIGFueSBrZXkgdG8gYmVnaW4uIgp9OwoKLy8gcHVzaCB0byB0aW1lbGluZQp0aW1lbGluZS5wdXNoKHdlbGNvbWUpOwoKLy9ydW4gdGhlIGV4cGVyaW1lbnQKanNQc3ljaC5ydW4odGltZWxpbmUpOwogIAo8L3NjcmlwdD4KCjwvaHRtbD4KCmBgYAoKCgoKCgoKCgpgYGB7ciBlY2hvPUZBTFNFLCBldmFsPVRSVUUsIHdhcm5pbmc9RkFMU0V9Cmh0bWx0b29sczo6dGFncyRzY3JpcHQoc3JjID0gImpzL3RyYW5zbGF0ZS5qcyIpCiMgaHRtbHRvb2xzOjp0YWdzJHNjcmlwdChzcmMgPSAianMvaW5mb2JveC5qcyIpCmh0bWx0b29sczo6dGFncyRzY3JpcHQoc3JjPSIvL3RyYW5zbGF0ZS5nb29nbGUuY29tL3RyYW5zbGF0ZV9hL2VsZW1lbnQuanM/Y2I9Z29vZ2xlVHJhbnNsYXRlRWxlbWVudEluaXQiKQoKaHRtbHRvb2xzOjp0YWdMaXN0KAogIHhhcmluZ2FuRXh0cmE6OnVzZV9jbGlwYm9hcmQoCiAgICBidXR0b25fdGV4dCA9ICI8aSBjbGFzcz1cImZhIGZhLWNsaXBib2FyZFwiIHN0eWxlPVwiZm9udC1zaXplOiAyNXB4XCI+PC9pPiIsCiAgICBzdWNjZXNzX3RleHQgPSAiPGkgY2xhc3M9XCJmYSBmYS1jaGVja1wiIHN0eWxlPVwiY29sb3I6ICM5MEJFNkQ7IGZvbnQtc2l6ZTogMjVweFwiPjwvaT4iLAogICksCiAgcm1hcmtkb3duOjpodG1sX2RlcGVuZGVuY3lfZm9udF9hd2Vzb21lKCkKKQoKYGBgCgpgYGB7anMgZWNobz1GQUxTRSwgZXZhbD1UUlVFfQp2YXIgY29sbCA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoImNvbGxhcHNpYmxlMSIpOwp2YXIgaTsKCmZvciAoaSA9IDA7IGkgPCBjb2xsLmxlbmd0aDsgaSsrKSB7CiAgY29sbFtpXS5hZGRFdmVudExpc3RlbmVyKCJjbGljayIsIGZ1bmN0aW9uKCkgewogICAgdGhpcy5jbGFzc0xpc3QudG9nZ2xlKCJhY3RpdmUxIik7CiAgICB2YXIgY29udGVudCA9IHRoaXMubmV4dEVsZW1lbnRTaWJsaW5nOwogICAgaWYgKGNvbnRlbnQuc3R5bGUubWF4SGVpZ2h0KXsKICAgICAgY29udGVudC5zdHlsZS5tYXhIZWlnaHQgPSBudWxsOwogICAgfSBlbHNlIHsKICAgICAgY29udGVudC5zdHlsZS5tYXhIZWlnaHQgPSBjb250ZW50LnNjcm9sbEhlaWdodCArICJweCI7CiAgICB9CiAgfSk7Cn0KCmBgYAo=